////////////////////////////////////////////////////////////////////////////
//
//  Crytek Engine Source File.
//  Copyright (C), Crytek Studios, 2011.
// -------------------------------------------------------------------------
//  File name:   FlowGraphDebugger.cpp
//  Version:     v1.00
//  Created:     13/09/2011 by Sascha Hoba.
//  Description: 
// -------------------------------------------------------------------------
//  History:
//
////////////////////////////////////////////////////////////////////////////

#include "StdAfx.h"
#include "FlowGraphDebuggerEditor.h"

#include "FlowGraphManager.h"
#include "HyperGraph.h" //IsFlowgraph check
#include "FlowGraphNode.h"
#include "IGameFramework.h" //Pause game
#include "IHardwareMouse.h" //SetGameMode
#include "HyperGraphDialog.h"
#include "FlowGraph.h"
#include "UI/UIManager.h" //pause updates
#include "GameEngine.h" //enable flowsystemupdate in game engine
#include <IFlowGraphModuleManager.h>

#define VALIDATE_DEBUGGER(a) if (!a) return false;
#define VALIDATE_DEBUGGER_VOID(a) if (!a) return;

CFlowGraphDebuggerEditor::CFlowGraphDebuggerEditor(): m_InGame(false), m_CursorVisible(false)
{
}

CFlowGraphDebuggerEditor::~CFlowGraphDebuggerEditor()
{
}

bool CFlowGraphDebuggerEditor::Init()
{
	if (GetIEditor()->GetFlowGraphManager())
	{
		GetIEditor()->GetFlowGraphManager()->AddListener(this);
	}
	else
	{
		CryWarning(VALIDATOR_MODULE_EDITOR, VALIDATOR_WARNING, "Flowgraph Manager doesn't exist [CFlowgraphDebugger::Init]");
		return false;
	}

	if (gEnv->pFlowSystem)
	{
		m_pFlowGraphDebugger = GetIFlowGraphDebuggerPtr();

		CRY_ASSERT(m_pFlowGraphDebugger);
		if (!m_pFlowGraphDebugger || !m_pFlowGraphDebugger->RegisterListener(this, "CFlowgraphDebuggerEditor"))
		{
			CryWarning(VALIDATOR_MODULE_EDITOR, VALIDATOR_WARNING, "Could not register as Flowgraph Debugger listener! [CFlowgraphDebugger::Init]");
		}
	}
	else
	{
		CryWarning(VALIDATOR_MODULE_EDITOR, VALIDATOR_WARNING, "Flowgraph System doesn't exist [CFlowgraphDebugger::Init]");
		return false;
	}

	GetIEditor()->RegisterNotifyListener(this);

	return true;
}

bool CFlowGraphDebuggerEditor::Shutdown()
{
	CFlowGraphManager* pFlowgraphManager = GetIEditor()->GetFlowGraphManager();
	bool bError = false;

	if (pFlowgraphManager)
	{
		pFlowgraphManager->RemoveListener(this);
	}
	else
	{
		CryWarning(VALIDATOR_MODULE_EDITOR, VALIDATOR_WARNING, "Flowgraph Manager doesn't exist [CFlowgraphDebugger::~CFlowgraphDebugger]");
		bError = true;
	}

	GetIEditor()->UnregisterNotifyListener(this);

	if (m_pFlowGraphDebugger)
	{
		m_pFlowGraphDebugger->UnregisterListener(this);
	}
	else
	{
		bError = true;
	}

	return !bError;
}

bool CFlowGraphDebuggerEditor::AddBreakpoint( CFlowNode* pFlowNode, CHyperNodePort* pHyperNodePort )
{
	VALIDATE_DEBUGGER(m_pFlowGraphDebugger);
	
	SFlowAddress addr;
	addr.node = pFlowNode->GetFlowNodeId();
	addr.port = pHyperNodePort->nPortIndex;
	addr.isOutput = !pHyperNodePort->bInput;

	const bool result = m_pFlowGraphDebugger->AddBreakpoint(pFlowNode->GetIFlowGraph(), addr);

	if (result)
		pFlowNode->Invalidate(true, false);

	return result;
}

bool CFlowGraphDebuggerEditor::RemoveBreakpoint( CFlowNode* pFlowNode, CHyperNodePort* pHyperNodePort )
{
	VALIDATE_DEBUGGER(m_pFlowGraphDebugger);

	SFlowAddress addr;
	addr.node = pFlowNode->GetFlowNodeId();
	addr.port = pHyperNodePort->nPortIndex;
	addr.isOutput = !pHyperNodePort->bInput;

	const bool result = m_pFlowGraphDebugger->RemoveBreakpoint(pFlowNode->GetIFlowGraph(), addr);

	if (result)
		pFlowNode->Invalidate(true, false);

	return result;
}

bool CFlowGraphDebuggerEditor::EnableBreakpoint( CFlowNode* pFlowNode, CHyperNodePort* pHyperNodePort, bool enable )
{
	VALIDATE_DEBUGGER(m_pFlowGraphDebugger);

	SFlowAddress addr;
	addr.node = pFlowNode->GetFlowNodeId();
	addr.port = pHyperNodePort->nPortIndex;
	addr.isOutput = !pHyperNodePort->bInput;

	const bool result = m_pFlowGraphDebugger->EnableBreakpoint(pFlowNode->GetIFlowGraph(), addr, enable);

	if (result)
		pFlowNode->Invalidate(true, false);

	return result;
}

bool CFlowGraphDebuggerEditor::EnableTracepoint( CFlowNode* pFlowNode, CHyperNodePort* pHyperNodePort, bool enable )
{
	VALIDATE_DEBUGGER(m_pFlowGraphDebugger);

	SFlowAddress addr;
	addr.node = pFlowNode->GetFlowNodeId();
	addr.port = pHyperNodePort->nPortIndex;
	addr.isOutput = !pHyperNodePort->bInput;

	const bool result = m_pFlowGraphDebugger->EnableTracepoint(pFlowNode->GetIFlowGraph(), addr, enable);

	if (result)
		pFlowNode->Invalidate(true, false);

	return result;
}

bool CFlowGraphDebuggerEditor::RemoveAllBreakpointsForNode( CFlowNode* pFlowNode )
{
	VALIDATE_DEBUGGER(m_pFlowGraphDebugger);

	const bool result = m_pFlowGraphDebugger->RemoveAllBreakpointsForNode(pFlowNode->GetIFlowGraph(), pFlowNode->GetFlowNodeId());

	CFlowGraph* pFlowgraph = GetIEditor()->GetFlowGraphManager()->FindGraph(pFlowNode->GetIFlowGraph());
	if (result && pFlowgraph)
		GetIEditor()->GetFlowGraphManager()->SendNotifyEvent( EHG_GRAPH_INVALIDATE, pFlowgraph );

	return result;
}

bool CFlowGraphDebuggerEditor::RemoveAllBreakpointsForGraph( IFlowGraphPtr pFlowGraph )
{
	VALIDATE_DEBUGGER(m_pFlowGraphDebugger);

	const bool result = m_pFlowGraphDebugger->RemoveAllBreakpointsForGraph(pFlowGraph);

	CFlowGraph* pGraph = GetIEditor()->GetFlowGraphManager()->FindGraph(pFlowGraph);
	if (result && pGraph)
		GetIEditor()->GetFlowGraphManager()->SendNotifyEvent( EHG_GRAPH_INVALIDATE, pGraph );

	return result;
}

bool CFlowGraphDebuggerEditor::HasBreakpoint( IFlowGraphPtr pFlowGraph, TFlowNodeId nodeID ) const
{
	VALIDATE_DEBUGGER(m_pFlowGraphDebugger);
	return m_pFlowGraphDebugger->HasBreakpoint(pFlowGraph, nodeID);
}

bool CFlowGraphDebuggerEditor::HasBreakpoint( CFlowNode* pFlowNode, const CHyperNodePort* pHyperNodePort ) const
{
	VALIDATE_DEBUGGER(m_pFlowGraphDebugger);

	SFlowAddress addr;
	addr.node = pFlowNode->GetFlowNodeId();
	addr.port = pHyperNodePort->nPortIndex;
	addr.isOutput = !pHyperNodePort->bInput;

	return m_pFlowGraphDebugger->HasBreakpoint(pFlowNode->GetIFlowGraph(), addr);
}

bool CFlowGraphDebuggerEditor::HasBreakpoint( IFlowGraphPtr pFlowGraph ) const
{
	VALIDATE_DEBUGGER(m_pFlowGraphDebugger);
	return m_pFlowGraphDebugger->HasBreakpoint(pFlowGraph);
}

void CFlowGraphDebuggerEditor::PauseGame( )
{
	VALIDATE_DEBUGGER_VOID(m_pFlowGraphDebugger);

	const SBreakPoint& breakpoint = m_pFlowGraphDebugger->GetBreakpoint();
	IFlowGraphPtr pFlowGraph = m_pFlowGraphDebugger->GetRootGraph(breakpoint.flowGraph);

	CFlowGraph* pGraph = GetIEditor()->GetFlowGraphManager()->FindGraph(pFlowGraph);

	if (pGraph)
	{
		GetIEditor()->GetFlowGraphManager()->OpenView(pGraph);

		if (CFlowNode* pNode = pGraph->FindFlowNode(breakpoint.addr.node))
		{
			CenterViewAroundNode(pNode);
			pGraph->UnselectAll();
			pNode->SetSelected(true);
		}
	}    

	gEnv->pFlowSystem->Enable(false);

	GetIEditor()->GetUIManager()->EnableUpdates(false);

	if (gEnv->pGame && (gEnv->pGame->GetIGameFramework()->IsGamePaused() == false))
		gEnv->pGame->GetIGameFramework()->PauseGame(true, true);

	if (gEnv->pHardwareMouse && (m_CursorVisible == false) && m_InGame)
	{
		gEnv->pHardwareMouse->SetGameMode(false);
		m_CursorVisible = true;
	}
}

void CFlowGraphDebuggerEditor::ResumeGame()
{
	VALIDATE_DEBUGGER_VOID(m_pFlowGraphDebugger);

	if (m_pFlowGraphDebugger->BreakpointHit())
	{
		bool bResume = true;

		const SBreakPoint& breakpoint = m_pFlowGraphDebugger->GetBreakpoint();

		IFlowGraphPtr pFlowGraph = m_pFlowGraphDebugger->GetRootGraph(breakpoint.flowGraph);

		if (pFlowGraph)
		{
			bResume = m_pFlowGraphDebugger->RePerformActivation();
		}

		CFlowGraph* pGraph = GetIEditor()->GetFlowGraphManager()->FindGraph(pFlowGraph);

		if (bResume)
		{
			if (gEnv->pHardwareMouse && m_CursorVisible)
			{
				gEnv->pHardwareMouse->SetGameMode(true);
				m_CursorVisible = false;
			}

			gEnv->pFlowSystem->Enable(true);
			GetIEditor()->GetUIManager()->EnableUpdates(true);

			if (gEnv->pGame)
				gEnv->pGame->GetIGameFramework()->PauseGame(false, true);

			// give the focus back to the main view
			AfxGetMainWnd()->SetFocus();
		}

		if (pGraph)
			GetIEditor()->GetFlowGraphManager()->SendNotifyEvent( EHG_GRAPH_INVALIDATE, pGraph );

		if (gEnv->pInput)
			gEnv->pInput->ClearKeyState();
	}
	else
	{
		GetIEditor()->GetGameEngine()->EnableFlowSystemUpdate(true);
	}
}

void CFlowGraphDebuggerEditor::CenterViewAroundNode( CFlowNode* pNode ) const
{
	CWnd* pWnd = GetIEditor()->FindView( "Flow Graph" );
	if (pWnd && pWnd->IsKindOf(RUNTIME_CLASS(CHyperGraphDialog)))
	{
		CHyperGraphDialog* pHyperGraphDialog = static_cast<CHyperGraphDialog*>(pWnd);
		CHyperGraphView* pGraphView = pHyperGraphDialog->GetGraphView();

		if(pGraphView)
		{
			pGraphView->CenterViewAroundNode(pNode, true);
			// we need the focus for the graph view (F5 to continue...)
			pGraphView->SetFocus();
		}
	}
}

void CFlowGraphDebuggerEditor::OnHyperGraphManagerEvent( EHyperGraphEvent event, IHyperGraph* pGraph, IHyperNode* pNode )
{
	if (!pNode)
		return;

	switch (event)
	{	
	case EHG_NODE_DELETE:
		{
			CHyperGraph* pHyperGraph = static_cast<CHyperGraph*>(pNode->GetGraph());
			// we have to check whether a deleted flownode has actually some breakpoints
			// and remove them too
			if (pHyperGraph->IsFlowGraph())
			{
				CFlowNode* pFlowNode = static_cast<CFlowNode*>(pNode);
				if (pFlowNode->IsFlowNode() && HasBreakpoint(pFlowNode->GetIFlowGraph(), pFlowNode->GetFlowNodeId()))
				{
					RemoveAllBreakpointsForNode(pFlowNode);
				}
			}
		}
		break;
	}
}

void CFlowGraphDebuggerEditor::OnEditorNotifyEvent( EEditorNotifyEvent event )
{
	switch (event)
	{
	case eNotify_OnBeginLoad:
		{
			if (m_pFlowGraphDebugger)
			{
				m_pFlowGraphDebugger->ClearBreakpoints();
			}

			CWnd* pWnd = GetIEditor()->FindView( "Flow Graph" );
			if (pWnd && pWnd->IsKindOf(RUNTIME_CLASS(CHyperGraphDialog)))
			{
				CHyperGraphDialog* pHyperGraphDialog = static_cast<CHyperGraphDialog*>(pWnd);
				CHyperGraphView* pGraphView = pHyperGraphDialog->GetGraphView();

				pHyperGraphDialog->ClearBreakpoints();

				if (pGraphView)
					pGraphView->InvalidateView(true);
			}
		}
		break;
	case eNotify_OnBeginGameMode:
		{
			if (gEnv->pHardwareMouse && m_CursorVisible)
			{
				gEnv->pHardwareMouse->SetGameMode(true);
				m_CursorVisible = false;
			}

			m_InGame = true;
		}
		break;
	case eNotify_OnEndGameMode:
	case eNotify_OnEndSimulationMode:
		{
			if (m_pFlowGraphDebugger && m_pFlowGraphDebugger->BreakpointHit())
				ResumeGame();

			m_InGame = false;
		}
		break;
	}
}

void CFlowGraphDebuggerEditor::OnBreakpointHit( const SBreakPoint& breakpoint )
{
	VALIDATE_DEBUGGER_VOID(m_pFlowGraphDebugger);

	IFlowGraphPtr pFlowGraph = m_pFlowGraphDebugger->GetRootGraph(breakpoint.flowGraph);
	CFlowGraph* pGraph = GetIEditor()->GetFlowGraphManager()->FindGraph(pFlowGraph);

	if (pGraph)
		GetIEditor()->GetFlowGraphManager()->SendNotifyEvent( EHG_GRAPH_INVALIDATE, pGraph );		

	PauseGame();
}

void CFlowGraphDebuggerEditor::OnBreakpointInvalidated( const SBreakPoint& breakpoint )
{
	VALIDATE_DEBUGGER_VOID(m_pFlowGraphDebugger);

	IFlowGraphPtr pFlowGraph = m_pFlowGraphDebugger->GetRootGraph(breakpoint.flowGraph);
	CFlowGraph* pFlowgraph = GetIEditor()->GetFlowGraphManager()->FindGraph(pFlowGraph);

	if (pFlowgraph)
		GetIEditor()->GetFlowGraphManager()->SendNotifyEvent( EHG_GRAPH_INVALIDATE, pFlowgraph );
}

void CFlowGraphDebuggerEditor::OnTracepointHit( const STracePoint& tracepoint )
{
	VALIDATE_DEBUGGER_VOID(m_pFlowGraphDebugger);

	IFlowGraphPtr pFlowGraph = m_pFlowGraphDebugger->GetRootGraph(tracepoint.flowGraph);
	CFlowGraph* pGraph = GetIEditor()->GetFlowGraphManager()->FindGraph(pFlowGraph);

	if (pGraph)
	{
		if (CFlowNode* pNode = pGraph->FindFlowNode(tracepoint.addr.node))
		{
			CHyperNodePort* pPort = pNode->FindPort(tracepoint.addr.port, !tracepoint.addr.isOutput);

			if(pPort)
			{
				string val;
				tracepoint.value.GetValueWithConversion(val);
				CryLog("$2[TRACEPOINT HIT - FrameID: %d] GRAPH: %s (ID: %d) - NODE: %s (ID: %d) - PORT: %s - VALUE: %s", gEnv->pRenderer->GetFrameID(), pGraph->GetName(), pGraph->GetIFlowGraph()->GetGraphId(), pNode->GetClassName(), pNode->GetId(), pNode->GetPortName(*pPort), val.c_str());
			}
		}
	}
}
